package pl.edu.pb.wi.pwnography.controller;

import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import pl.edu.pb.wi.pwnography.model.form.DecisionTreeForm;
import pl.edu.pb.wi.pwnography.modules.DecisionTree;
import pl.edu.pb.wi.pwnography.modules.DecisionTreeCustom;
import pl.edu.pb.wi.pwnography.modules.KNNAllAttributes;
import pl.edu.pb.wi.pwnography.session.FileList;
import pl.edu.pb.wi.pwnography.tree.Entropy;
import pl.edu.pb.wi.pwnography.tree.Node;
import pl.edu.pb.wi.pwnography.tree.Tree;
import quickdt.Attributes;
import quickdt.Instance;

@Controller
@RequestMapping("/module/dt")
public class DecisionTreeModule {
    private static final Logger log = Logger.getLogger(DecisionTreeModule.class
	    .getName());

    private static final String ASSESSMENT_QUALITY_CUSTOM_ALL_DESCRIPTION = "Ocena jakości klasyfikacji drzewa decyzyjnego (na entropii) metodą 'leave-one-out' dla klasy decyzyjnej '%s'. Wynik: %f W procentach: %f%%";
    private static final String ASSESSMENT_QUALITY_ALL_DESCRIPTION = "Ocena jakości klasyfikacji drzewa decyzyjnego metodą 'leave-one-out' dla klasy decyzyjnej '%s'. Wynik: %f W procentach: %f%%";
    private static final String ASSESSMENT_QUALITY_ERROR = "Wystąpił błąd w trakcie oceny jakości klasyfikacji.";

    @Autowired
    private FileList fileList;

    public void setFileList(FileList fileList) {
	this.fileList = fileList;
    }

    @ModelAttribute("floatColumnList")
    public List<String> getFloatColumnNames() {
	return fileList.getActiveFile().getFloatTypeColumnNames();
    }

    @ModelAttribute("stringColumnList")
    public List<String> getStringColumnNames() {
	return fileList.getActiveFile().getColumnNames();
    }

    @RequestMapping(value = "assessmentquality", method = RequestMethod.GET)
    public String assessmentQuality(Model model) {
	log.info("Assessment quality GET.");

	model.addAttribute("form", new DecisionTreeForm());

	return "module/dt/assessmentquality";
    }

    @RequestMapping(value = "assessmentquality", method = RequestMethod.POST)
    public String assessmentQuality(
	    @ModelAttribute("form") @Valid DecisionTreeForm form,
	    BindingResult results, Model model) {
	log.info("Assessment quality POST.");

	if (results.hasErrors()) {
	    return "module/dt/assessmentquality";
	}

	try {
	    List<Object> newClasses = DecisionTree.leaveOneOut(fileList
		    .getActiveFile(), form.getExcludedColumns(), form
		    .getColumnName(), form.getDiscretizationLevel(), fileList
		    .getActiveFile().getRowCount(), fileList.getActiveFile()
		    .getData());

	    float assessmentQuality = KNNAllAttributes.assessmentQuality(
		    fileList.getActiveFile().getColumnValues(
			    form.getColumnName()), newClasses);

	    String description = String.format(
		    ASSESSMENT_QUALITY_ALL_DESCRIPTION, form.getColumnName(),
		    assessmentQuality, assessmentQuality * 100);

	    if (form.getSaveToFile()) {
		Map<String, List<Object>> data;
		if (form.getResultColumnName() == null
			|| form.getResultColumnName().isEmpty())
		    data = DecisionTree.createColumn(form.getColumnName(),
			    newClasses, form.getDiscretizationLevel());
		else {
		    data = new LinkedHashMap<String, List<Object>>();
		    data.put(form.getResultColumnName(), newClasses);
		}

		fileList.getActiveFile().addNewRevision(data, description);
		log.info(fileList.getActiveFile().getCurrentRevisionData()
			.toString());
	    }

	    model.addAttribute("successMsgs",
		    Arrays.asList(new String[] { description }));
	} catch (Exception e) {
	    log.info(String
		    .format("Problem with Decision Tree asessment quality . Exception: %s",
			    e.getMessage()));

	    model.addAttribute(
		    "errorMsgs",
		    Arrays.asList(new String[] { ASSESSMENT_QUALITY_ERROR,
			    e.getMessage() }));
	}

	return "module/dt/assessmentquality";
    }

    @RequestMapping(value = "custom/assessmentquality", method = RequestMethod.GET)
    public String assessmentQualityCustom(Model model) {
	log.info("Assessment quality custom GET.");

	model.addAttribute("form", new DecisionTreeForm());

	return "module/dt/custom/assessmentquality";
    }

    @RequestMapping(value = "custom/assessmentquality", method = RequestMethod.POST)
    public String assessmentQualityCustom(
	    @ModelAttribute("form") @Valid DecisionTreeForm form,
	    BindingResult results, Model model) {
	log.info("Assessment quality custom POST.");

	if (results.hasErrors()) {
	    return "module/dt/custom/assessmentquality";
	}

	try {
	    List<Object> newClasses = DecisionTreeCustom.leaveOneOut(fileList
		    .getActiveFile(), form.getExcludedColumns(), form
		    .getColumnName(), form.getDiscretizationLevel(), fileList
		    .getActiveFile().getRowCount(), fileList.getActiveFile()
		    .getData());

	    float assessmentQuality = KNNAllAttributes.assessmentQuality(
		    fileList.getActiveFile().getColumnValues(
			    form.getColumnName()), newClasses);

	    String description = String.format(
		    ASSESSMENT_QUALITY_CUSTOM_ALL_DESCRIPTION,
		    form.getColumnName(), assessmentQuality,
		    assessmentQuality * 100);

	    if (form.getSaveToFile()) {
		Map<String, List<Object>> data;
		if (form.getResultColumnName() == null
			|| form.getResultColumnName().isEmpty())
		    data = DecisionTree.createColumn(form.getColumnName(),
			    newClasses, form.getDiscretizationLevel());
		else {
		    data = new LinkedHashMap<String, List<Object>>();
		    data.put(form.getResultColumnName(), newClasses);
		}

		fileList.getActiveFile().addNewRevision(data, description);
		log.info(fileList.getActiveFile().getCurrentRevisionData()
			.toString());
	    }

	    model.addAttribute("successMsgs",
		    Arrays.asList(new String[] { description }));
	} catch (Exception e) {
	    log.info(String
		    .format("Problem with Decision Tree asessment quality custom. Exception: %s",
			    e.getMessage()));

	    model.addAttribute(
		    "errorMsgs",
		    Arrays.asList(new String[] { ASSESSMENT_QUALITY_ERROR,
			    e.getMessage() }));
	}

	return "module/dt/custom/assessmentquality";
    }

    @RequestMapping(value = "test", method = RequestMethod.GET)
    public void test() {
	log.info("TEST.");

	Set<Instance> instances = new HashSet<Instance>();
	instances.add(Attributes.create("TEMPERATURA", "wysoka", "BÓL_GŁOWY",
		"tak", "SAMOPOCZUCIE", "złe").classification("tak"));
	instances.add(Attributes.create("TEMPERATURA", "b.wysoka", "BÓL_GŁOWY",
		"tak", "SAMOPOCZUCIE", "dobre").classification("tak"));
	instances.add(Attributes.create("TEMPERATURA", "normalna", "BÓL_GŁOWY",
		"nie", "SAMOPOCZUCIE", "złe").classification("nie"));
	instances.add(Attributes.create("TEMPERATURA", "wysoka", "BÓL_GŁOWY",
		"tak", "SAMOPOCZUCIE", "dobre").classification("tak"));
	instances.add(Attributes.create("TEMPERATURA", "wysoka", "BÓL_GŁOWY",
		"nie", "SAMOPOCZUCIE", "dobre").classification("nie"));
	instances.add(Attributes.create("TEMPERATURA", "normalna", "BÓL_GŁOWY",
		"tak", "SAMOPOCZUCIE", "złe").classification("nie"));
	instances.add(Attributes.create("TEMPERATURA", "normalna", "BÓL_GŁOWY",
		"nie", "SAMOPOCZUCIE", "dobre").classification("nie"));

	double classEntropy = Entropy.calculateClassEntropy(instances);
	double tmpEntropy = Entropy.calculateConditionalEntropy(instances,
		"TEMPERATURA");
	double bgEntropy = Entropy.calculateConditionalEntropy(instances,
		"BÓL_GŁOWY");

	log.info(String.format("\nClass: %f\nTEMP: %f\nBG: %f\n", classEntropy,
		tmpEntropy, bgEntropy));

	Tree tree = new Tree(instances);
	Node node = tree.build(instances, null, tree.getAttributes());
	log.info("\n\n");
	log.info(node.toString("="));

	Attributes nie1 = Attributes.create("TEMPERATURA", "normalna",
		"BÓL_GŁOWY", "nie", "SAMOPOCZUCIE", "dobre");
	Attributes nie2 = Attributes.create("TEMPERATURA", "normalna",
		"BÓL_GŁOWY", "tak", "SAMOPOCZUCIE", "złe");
	Attributes tak1 = Attributes.create("TEMPERATURA", "b.wysoka",
		"BÓL_GŁOWY", "tak", "SAMOPOCZUCIE", "dobre");
	log.info(node.classification(nie1).toString());
	log.info(node.classification(nie2).toString());
	log.info(node.classification(tak1).toString());

	// instances.add(Attributes.create("Outlook", "sunny", "Tempereature",
	// "hot", "Humidity", "high", "Windy", "weak")
	// .classification("no"));
	// instances.add(Attributes.create("Outlook", "sunny", "Tempereature",
	// "hot", "Humidity", "high", "Windy", "strong").classification(
	// "no"));
	// instances.add(Attributes.create("Outlook", "overcast",
	// "Tempereature",
	// "hot", "Humidity", "high", "Windy", "weak").classification(
	// "yes"));
	// instances.add(Attributes.create("Outlook", "rain", "Tempereature",
	// "mild", "Humidity", "high", "Windy", "weak").classification(
	// "yes"));
	// instances.add(Attributes.create("Outlook", "rain", "Tempereature",
	// "cool", "Humidity", "normal", "Windy", "weak").classification(
	// "yes"));
	// instances.add(Attributes.create("Outlook", "rain", "Tempereature",
	// "cool", "Humidity", "normal", "Windy", "strong")
	// .classification("no"));
	// instances.add(Attributes.create("Outlook", "overcast",
	// "Tempereature",
	// "cool", "Humidity", "normal", "Windy", "strong")
	// .classification("yes"));
	// instances.add(Attributes.create("Outlook", "sunny", "Tempereature",
	// "mild", "Humidity", "high", "Windy", "weak").classification(
	// "no"));
	// instances.add(Attributes.create("Outlook", "sunny", "Tempereature",
	// "cool", "Humidity", "normal", "Windy", "weak").classification(
	// "yes"));
	// instances.add(Attributes.create("Outlook", "rain", "Tempereature",
	// "mild", "Humidity", "normal", "Windy", "weak").classification(
	// "yes"));
	// instances.add(Attributes.create("Outlook", "sunny", "Tempereature",
	// "mild", "Humidity", "normal", "Windy", "strong")
	// .classification("yes"));
	// instances.add(Attributes.create("Outlook", "overcast",
	// "Tempereature",
	// "mild", "Humidity", "high", "Windy", "strong").classification(
	// "yes"));
	// instances.add(Attributes.create("Outlook", "overcast",
	// "Tempereature",
	// "hot", "Humidity", "normal", "Windy", "weak").classification(
	// "yes"));
	// instances.add(Attributes.create("Outlook", "rain", "Tempereature",
	// "mild", "Humidity", "high", "Windy", "strong").classification(
	// "no"));
    }
}
